// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.


#pragma once

#include <type_traits>

#include "data_basic.hpp"
#include "scheduler_tick.hpp"


namespace scheduler_basic {

    template <typename TimeType>
    class TaskObjectBase;


    template <typename TimeType>
    TimeType call_task_object(TaskObjectBase<TimeType> *tptr);


    /**
     * @brief 存储任务信息的结构体
     *
     * 由于这只是个简单的调度器，任务结构体也就尽量简单，只有回调函数的指针和计时器这两个必要的成员。
     * 任务计时采用倒计时原理，每次在tick 函数中更新所有任务的倒计时数值，原因是避免计时器溢出导致BUG。
     *
     * 调度器只支持在一段时间后运行指定的任务，本身不支持循环执行，这也是为了实现简单。想实现延迟指定时间后运行的功能，一般可以有三种思路：
     *
     * 1. 任务结构体内存储一个时间戳T1，代表预定要执行的时间。由于计时器溢出的问题，这种设计容易BUG。
     *
     * 2. 任务结构体内记录添加任务时的时间戳T0 和延迟时间ΔT，用当前时间T 减去T0 得到时间间隔，再和ΔT 比较。
     *    这是Arduino 社区推荐的delay 函数的实现方式，不会因为一次计时器溢出导致BUG，最大延时时间为一个溢出周期。
     *    但结构体里要多放一个TimeType 类型的变量，空间效率差一点。
     *
     * 3. 结构体里只放一个倒计时器，也就是这里用的方法，每次tick 函数检查任务状态时更新所有任务的倒计时器，
     *    倒计时器减去的值等于两次tick 执行的间隔，当任务的倒计时剩余时间小于等于要减去的间隔时，说明任务延时结束。
     */
    template <typename TimeType = uint32_t>
    struct TaskBox {
        void *fptr = nullptr;   // 函数指针，或者指向TaskObjectBase 的指针，具体是哪种，取决于外部的标志。
                                // 不添加内部标志，因为外部的实现可能更高效
        TimeType down_counter;  // down_counter 有三个特殊值：
                                //  1. down_counter == 0, 表示任务已被删除，TaskBox 可被重用
                                //  2. down_counter == 1, 表示任务就绪，下一次tick 就应该运行
                                //  3. down_counter == MAX, MAX 是TimeType 类型能表达的最大值，
                                //     表示fptr 指向的任务对象已析构，任务应该被删除。
                                //
                                //  添加任务时，不能将down_counter 设为0 或MAX。调度运行时，
                                //  down_counter 是单调递减的，到0 为止，不会意外溢出变成MAX。
        /**
         * @brief 将fptr 作为函数指针调用
         *
         * @return TimeType
         */
        TimeType call() {
            auto p = reinterpret_cast<TimeType (*)()>(fptr);
            return p();
        }

        /**
         * @brief 将fptr 作为TaskObjectBase 指针，调用它内部的成员函数指针
         *
         * @return TimeType
         */
        TimeType call_object() {
            auto p = reinterpret_cast<TaskObjectBase<TimeType> *>(fptr);
            return call_task_object<TimeType>(p);
        }
    };


    template <typename TimeType>
    class TaskObjectBase {
       public:
        static constexpr uint8_t TASK_FINISHED_CODE = 0;
        static constexpr uint8_t TASK_DESTROYED_CODE = static_cast<TimeType>(0) - 1;

        TimeType (*fptr)(void *) = nullptr;    // 指向子类的任务函数的函数指针，相当于虚函数
        TaskBox<TimeType> *box_ptr = nullptr;  // 将TaskObjectBase 添加到任务列表中时，
                                               // 存储引用这个TaskObjectBase 的TaskBox 指针。
                                               // 需要注意，这里形成了环形引用，所以不得不用前向声明。

        ~TaskObjectBase() {
            // 任务对象析构时，应该将它自己从列表里删除
            //
            // 有一个副作用：当一个任务对象被重复加入任务列表时，box_ptr 指向最后一次加入的TaskBox，
            // 析构时也只会影响最后一次的TaskBox，之前的TaskBox 如果还在列表里，就会持有野指针。
            // 不能用fptr 作为析构标志，因为对象被释放后，原来的内存会变成什么样是随机的。
            //
            // 任务对象的析构有几种情况
            // 1. 析构时对象还在任务列表里
            //    此时应该将对象从列表里移除，方法就是将down_counter 置零；
            //    调度器检测到down_counter 为0 后，删除任务
            // 2. 析构时对象不在列表里
            //    此时任务对象不应该操作TaskBox，因为这个TaskBox 可能已经是别的任务了；
            //    但是调度器删除任务时，不能把任务对象的fptr 置为nullptr，因为删除任务时，
            //    任务对象可能已经销毁了。
            //
            // 任务对象必须知道它是不是还在列表里，所以调度器必须在删除任务时设置个标志，又因为TaskBox 是要重用的，
            // 所以这个标志不能放在TaskBox 里，只能放在任务对象里，可以用box_ptr 作为标志。
            // 但是调度器想访问任务对象，又必须知道对象是不是已经析构了，
            // 所以又需要一个析构标志。这个析构标志当然必须放在TaskBox 里，当任务还没被删除时，
            // TaskBox 不会被重用，所以析构标志会维持有效。

            if (box_ptr != nullptr) {
                // 将down_counter 设为MAX，表示对象已析构
                box_ptr->down_counter = static_cast<TimeType>(0) - 1;
            }
        }

        bool is_in_list() const {
            return box_ptr != nullptr;
        }
    };


    template <typename TimeType>
    TimeType call_task_object(TaskObjectBase<TimeType> *tptr) {
        auto f = tptr->fptr;
        auto vt = reinterpret_cast<void *>(tptr);
        return f(vt);
    }


    template <typename Child, typename TimeType = uint32_t>
    class TaskObject : public TaskObjectBase<TimeType> {
       private:
        static TimeType _run_task(void *ptr) {
            auto *p = reinterpret_cast<Child *>(ptr);
            return p->task();
        }

       public:
        TaskObject() {
            // 成员函数不能直接转换成函数指针
            // 只能用一个普通的函数作为中转
            this->fptr = _run_task;
        }
    };
}  // namespace scheduler_basic